#region Copyright
//       Copyright (c) 2009 - Antoine Jaussoin - http://www.jaussoin.com/blog/
// 
//        This program is free software: you can redistribute it and/or modify
//        it under the terms of the GNU General Public License as published by
//        the Free Software Foundation, either version 3 of the License, or
//        (at your option) any later version.
// 
//        This program is distributed in the hope that it will be useful,
//        but WITHOUT ANY WARRANTY; without even the implied warranty of
//        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//        GNU General Public License for more details.
// 
//        You should have received a copy of the GNU General Public License
//        along with this program.  If not, see <http://www.gnu.org/licenses/>.
#endregion
using System;
using System.Reflection;

namespace FT.Architecture.Utils.Reflection
{
    /// <summary>
    /// Allows to access properties of an object via their name through reflection
    /// </summary>
    /// <typeparam name="T">Type of the object you want its properties from</typeparam>
    public class Reflector<T>
    {
        private T @object;
        private readonly bool ignoreNullIntermediateProperties;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="object"></param>
        public Reflector(T @object) : this(@object, false)
        {
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="object"></param>
        /// <param name="ignoreNullIntermediateProperties"></param>
        public Reflector(T @object, bool ignoreNullIntermediateProperties)
        {
            this.@object = @object;
            this.ignoreNullIntermediateProperties = ignoreNullIntermediateProperties;
        }

        /// <summary>
        /// Object we get/set the properties from
        /// </summary>
        public T Object
        {
            get { return @object; }
            set { @object = value; }
        }

        /// <summary>
        /// If <see langword="true"/>, doesn't throw an exception when an intermediate property is <see langword="null"/>
        /// </summary>
        public bool IgnoreNullIntermediateProperties
        {
            get { return ignoreNullIntermediateProperties; }
        }

        /// <summary>
        /// Get the value of a property of type S
        /// </summary>
        /// <typeparam name="TProperty">Type of the property</typeparam>
        /// <param name="propertyName">Name of the property</param>
        /// <returns></returns>
        public TProperty GetProperty<TProperty>(string propertyName)
        {
            return Reflection.GetProperty<TProperty>(@object, propertyName, ignoreNullIntermediateProperties);
        }

        /// <summary>
        /// Get the value of a property as a string
        /// </summary>
        /// <param name="propertyName">Name of the property</param>
        /// <returns></returns>
        public string GetProperty(string propertyName)
        {
            return Reflection.GetProperty<string>(@object, propertyName, ignoreNullIntermediateProperties);
        }

        /// <summary>
        /// Sets the property to another value. This is recursive, so you can give a <paramref name="propertyName"/> such as User.FirstName
        /// </summary>
        /// <param name="propertyName"></param>
        /// <param name="newValue"></param>
        public void SetProperty(string propertyName, object newValue)
        {
            if (!propertyName.Contains("."))
            {
                PropertyInfo pi = Reflection.GetProperty(@object, propertyName);
                pi.SetValue(@object, newValue, null);
            }
            else
            {
                object o = GetProperty<object>(propertyName.Split('.')[0]);
                Reflector<object> r = new Reflector<object>(o);
                string newPropName =
                    propertyName.Substring(propertyName.IndexOf('.') + 1,
                                           propertyName.Length - propertyName.IndexOf('.') - 1);
                r.SetProperty(newPropName, newValue);
            }
        }

        /// <summary>
        /// Sets the field to another value. This is NOT recursive
        /// </summary>
        /// <param name="fieldName"></param>
        /// <param name="newValue"></param>
        public void SetField(string fieldName, object newValue)
        {
            if (!fieldName.Contains("."))
            {
                FieldInfo fi = @object.GetType().GetField(fieldName,
                                                          BindingFlags.FlattenHierarchy | BindingFlags.NonPublic |
                                                          BindingFlags.Instance);
                if (fi != null) fi.SetValue(@object, newValue);
            }
            else
            {
                throw new NotImplementedException("This method can't be used recursively");
            }
        }
    }
}